package dcore.id;

import com.alibaba.cloud.nacos.ConditionalOnNacosDiscoveryEnabled;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.discovery.NacosWatch;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.listener.NamingEvent;
import com.alibaba.nacos.api.naming.pojo.Instance;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.CommonsClientAutoConfiguration;
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


/**
 * @author djin
 *
 */
@Slf4j
@Component
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureBefore({SimpleDiscoveryClientAutoConfiguration.class, CommonsClientAutoConfiguration.class})
public class SnowflakeByNacos implements Snowflake {
    private NamingService namingService;
    private Instance currentInstance;
    @Autowired
    private NacosDiscoveryProperties nacosProperties;
    @Autowired
    private NacosIdConfiguration config;
    /**
     * 最后检查workId时间戳
     */
    private long checkWorkidTime =0;
    /**
     * 当前机器码
     */
    private static long machineCode = -1;
    /**
     * 最后时间码
     */
    private static long latestTimeCode = 0L;
    /**
     * 序号
     */
    private static int sequence = 0;
    /**
     * 机器码检查线程
     */
    private static ExecutorService machineCheckThread = Executors.newSingleThreadExecutor();

    public SnowflakeByNacos(){
        IdUtil.setSnowflake(this);
    }

    /**
     * 获取机器码
     *
     * @return 机器码
     */
    @Override
    public long getMachineCode() {
        return getMachineCode(Boolean.FALSE);
    }

    /**
     * 获取机器码
     * @param enforce
     * @return
     */
    @SneakyThrows
    private long getMachineCode(boolean enforce) {
        if(!enforce && machineCode > 0){
            return machineCode;
        }
        List<Instance> instanceList = getAllInstance();

        if (instanceList.isEmpty() || getCurrentInstance() == null) {
            machineCode = 0;
            return machineCode;
        }
        List<String> workList = instanceList.stream().map(ins -> String.format("%s-%s-%s", ins.getIp(), ins.getPort(), ins.getClusterName())).sorted().toList();
        String id = String.format("%s-%s-%s", getCurrentInstance().getIp(), getCurrentInstance().getPort(), getCurrentInstance().getClusterName());
        for (int i = 0, len = workList.size(); i < len; i++) {
            if (workList.get(i).equals(id)) {
                machineCode = i + 1;
                break;
            } else if (i == len - 1) {
                machineCode = 0;
            }
        }

        if (machineCode > config.getMachineMask()) {
            machineCode = 0;
            throw new RuntimeException("机器码已达到最大值，无法继续生成！");
        }

        return machineCode;
    }

    /**
     * 获取时间码
     *
     * @return 时间码
     */
    @Override
    public long getTimeCode() {
        long timestamp = System.currentTimeMillis() / 1000 - config.getMinTimestamp();
        if (timestamp < 0) {
            throw new IdException("系统时间错误，请修改服务器系统时间");
        }
        return timestamp;
    }

    /**
     * 获取序号,如果返回0表示ID已用完
     *
     * @return 序号
     */
    @Override
    public int getSequenceCode() {
        sequence = (sequence + 1) & config.getSequenceMask();
        return sequence;
    }

    /**
     * 获取下一个ID
     *
     * @return ID
     */
    @Override
    public long nextId() {
        long timeCode = getTimeCode();
        long macCode = getMachineCode();

        if(timeCode > latestTimeCode) {
            sequence = 0;
            latestTimeCode = timeCode;
        } else {
            sequence = getSequenceCode();
            //如果seqCode已用完，循环获取下一个ID
            if(sequence == 0) {
                while(timeCode == latestTimeCode) {
                    timeCode = getTimeCode();
                }
                latestTimeCode = timeCode;
            }
        }
        // 异步线程检查并重置机器码
        machineCheckThread.execute(()->{
            checkAndResetWorkId(Boolean.FALSE);
        });
        return timeCode << config.getStampBit() | macCode << config.getSequenceBit() | sequence;
    }

    /**
     * 初始化实例时设置微服务监听，当微服务实例发生变化时，自动处理机器码
     *
     * @param nacosServiceManager
     * @param nacosDiscoveryProperties
     * @return
     * @throws NacosException
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(value = {"spring.cloud.nacos.discovery.watch.enabled"}, matchIfMissing = true)
    public NacosWatch nacosWatch(NacosServiceManager nacosServiceManager, NacosDiscoveryProperties nacosDiscoveryProperties) throws NacosException {
        namingService = nacosServiceManager.getNamingService();

        listenService(nacosServiceManager, nacosDiscoveryProperties);
        return new NacosWatch(nacosServiceManager, nacosDiscoveryProperties);
    }

    /**
     * 监听服务变更，当有新的实例注册时，自动触发事件
     *
     * @param nacosServiceManager
     * @param nacosDiscoveryProperties
     * @throws NacosException
     */
    private void listenService(NacosServiceManager nacosServiceManager, NacosDiscoveryProperties nacosDiscoveryProperties) throws NacosException {
        nacosServiceManager.getNamingService().subscribe(nacosDiscoveryProperties.getService(), nacosDiscoveryProperties.getGroup(), List.of(nacosDiscoveryProperties.getClusterName()), e -> {
            log.info("触发Nacos微服务监听事件");
            if (e instanceof NamingEvent) {
                checkAndResetWorkId(Boolean.TRUE);
            }
        });
        log.info("订阅微服务监听事件，微服务名称：{}", nacosDiscoveryProperties.getService());
    }

    /**
     * 检查并重置workId
     *
     * @param enforce 强制检查并重置workId
     */
    private void checkAndResetWorkId(boolean enforce) {
        if (!enforce) {
            long times = System.currentTimeMillis() - checkWorkidTime;
            if (times < config.getMachineCheckThreshold()) {
                return;
            }
        }
        long oldWorkId = machineCode;
        getMachineCode(enforce);
        checkWorkidTime = System.currentTimeMillis();
        log.info("WorkId更新，原WorkId为:{}, 更新后为:{};", oldWorkId, machineCode);
    }

    /**
     * 获取所有上线状态(不包括下线状态)的实例
     *
     * @return
     * @throws NacosException
     */
    private List<Instance> getAllInstance() throws NacosException {
        return namingService.getAllInstances(nacosProperties.getService(), nacosProperties.getGroup(), List.of(nacosProperties.getClusterName()));
    }

    /**
     * 获取当前服务实例，如果服务实例为NULL，则从Nacos获取实例
     *
     * @return
     * @throws NacosException
     */
    private Instance getCurrentInstance() throws NacosException {
        if (currentInstance != null) {
            return currentInstance;
        }
        List<Instance> instanceList = getAllInstance();
        Optional<Instance> optionalInstance = instanceList.stream().filter(ins -> ins.getIp().equals(nacosProperties.getIp()) && ins.getPort() == nacosProperties.getPort() && ins.getClusterName().equals(nacosProperties.getClusterName())).findFirst();
        currentInstance = optionalInstance.orElse(null);
        return currentInstance;
    }
}
